#include <iostream>
#include <chrono>
#include <vector>
#include "TimeWheel.h"
#include "windows.h"
#include "IntrusiveList.h"
#include "TimeWheelWrapper.h"

using namespace std::chrono;
using namespace TimeWheel;
using namespace IntrusiveList;

auto tCurr = 100000000;

#define ADD_TIMER(idx, timeout, ...) auto t##idx = CTimer::CreateNew();\
	t##idx->Start(tw, timeout, OnTimer, (void*)timeout, __VA_ARGS__);\

template<class T>
void TraverseList(List<T>& lst)
{
	if (lst.IsEmpty())
	{
		std::cout << "List is empty." << std::endl;
		return;
	}

	int i = 0;
	for (ListNode<T>* pNode = lst.First(); pNode != nullptr; pNode = pNode->pNext, ++i)
	{
		auto pValue = pNode->GetOwner();
		if (pValue == nullptr)
		{
			break;
		}
		std::cout << "i:" << *pValue << " ";

		if (pNode == lst.Last())
		{
			break;
		}
	}

	std::cout << std::endl;
}

struct TestListEntry
{
	int nValue;
	ListNode<TestListEntry> node;

	TestListEntry(int n)
	{
		nValue = n;
	}

	friend std::ostream& operator<<(std::ostream& os, const TestListEntry& pEntry)
	{
		os << pEntry.nValue;

		return os;
	}

};

void TestList()
{
	List<TestListEntry> lst;

	TraverseList(lst);

	srand((uint32_t)time(NULL));
	std::vector<std::shared_ptr<TestListEntry>> vec;
	for (int i = 0; i < 10; ++i)
	{
		auto pEntry = std::make_shared<TestListEntry>(i);
		vec.push_back(pEntry);
		pEntry->node.SetOwner(pEntry);
		
		int nRand = rand();
		if (nRand % 2 == 0)
		{
			lst.Add(pEntry->node);
		}
		else
		{
			lst.AddTail(pEntry->node);
		}
		TraverseList(lst);
	}

	for (auto pNode = lst.First(); pNode != nullptr; pNode = lst.First())
	{
		lst.Remove(*pNode);
		TraverseList(lst);
	}
}

void OnTimer(void* arg)
{
	printf("Curr[%u]: Timer[%u]\n", tCurr, (int)arg);
}

class CTestTimer
{
public:
	CTestTimer()
	{
		n = rand();
		m_pTimer = CTimer::CreateNew();
		std::cout << __FUNCTION__ << ": n = " << n << std::endl;
	}

	void AddTimer(CTimeWheel* pTw, int64_t nTimeout)
	{
		m_pTimer->Start(pTw, nTimeout, [this](void* arg) { this->OnTimer(arg); }, (void*)n);
	}

	void AddDelSelfTimer(CTimeWheel* pTw, int64_t nTimeout)
	{
		m_pTimer->Start(pTw, nTimeout, [this](void* arg) { static int n = 0; this->OnTimer(arg); if (++n == 3) delete this; }, (void*)n, TimerPeriodic::TP_TRUE);
	}

	void OnTimer(void* arg)
	{
		std::cout << __FUNCTION__ << ": n = " << n << std::endl;
	}
private:
	int n;
	std::shared_ptr<CTimer> m_pTimer;
};

void TestTimer()
{
	auto tw = CTimeWheel::CreateNew();
	if (!tw->Create(tCurr))
	{
		return;
	}

	CTestTimer obj;
	obj.AddTimer(tw, 5);

	CTestTimer* pObj = new CTestTimer;
	pObj->AddDelSelfTimer(tw, 10);

	ADD_TIMER(51, 0);

	ADD_TIMER(1, 1);

	ADD_TIMER(52, 0);

	ADD_TIMER(53, 8);

	ADD_TIMER(2, 255);

	ADD_TIMER(3, 32767);

	ADD_TIMER(4, 1000);

	ADD_TIMER(5, 3600000);

	ADD_TIMER(6, 300000);

	ADD_TIMER(7, 5000);

	ADD_TIMER(8, 5000);

	ADD_TIMER(9, 256);

	ADD_TIMER(10, 65536);

	ADD_TIMER(20, 10000, TimerPeriodic::TP_TRUE);

	for (int i = 0; i < 100000; ++i)
	{
		if (i < 10)
		{
			std::cout << "remain :" << tw->GetRemain(t51) << std::endl;
		}

		tw->RunTicks(tCurr++);
	}
	
	{
		ADD_TIMER(11, 1);
	}
	ADD_TIMER(12, 255);

	ADD_TIMER(13, 32767);

	ADD_TIMER(14, 1000);

	const time_t tOutofRange = uint64_t(UINT_MAX);
	ADD_TIMER(15, tOutofRange);

	while (1)
	{
		tw->RunTicks(tCurr++);

		if (tCurr > 101000000)
		{
			break;
		}
	}

	printf("fast forward begin...\n");
	ADD_TIMER(30, 100000, TimerPeriodic::TP_TRUE);
	ADD_TIMER(31, 10, TimerPeriodic::TP_FALSE);
	ADD_TIMER(32, 100, TimerPeriodic::TP_FALSE);
	ADD_TIMER(33, 1000, TimerPeriodic::TP_FALSE);
	ADD_TIMER(34, 500, TimerPeriodic::TP_TRUE);
	ADD_TIMER(35, 5000, TimerPeriodic::TP_TRUE);

	tCurr += 10000;
	tw->RunTicks(tCurr);
	printf("fast forward end...\n");
	
	for (int i = 0; i < 100000; ++i)
	{
		tw->RunTicks(tCurr++);
	}
#ifndef _DEBUG
	const int TEST_TIMES = 1000000;
	srand((int32_t)time(NULL));
	std::vector<std::shared_ptr<CTimer>> vecTimer;
	steady_clock sc;

	auto tp1 = sc.now();
	for(int i = 0; i < TEST_TIMES; ++i)
	{
		
		auto p = CTimer::CreateNew();
		vecTimer.push_back(p);
	}
	auto tp2 = sc.now();
	for (auto& t : vecTimer)
	{
		time_t tTimeout = rand();
		t->Start(tw, tTimeout, OnTimer, (void*)tCurr);
	}
	auto tp3 = sc.now();

	auto cost1 = duration_cast<std::chrono::milliseconds>(tp2 - tp1).count();
	auto cost2 = duration_cast<std::chrono::milliseconds>(tp3 - tp2).count();
	printf("Test add timer %d times cost %lld ms(alloc:%lldms add:%lldms). sizeof(CTimer)=%zd.\n", TEST_TIMES, cost1+cost2, cost1, cost2, sizeof(CTimer));
#endif

	tw->Release();
	tw = nullptr;
}

void TestWrapper()
{
	printf("TestWrapper begin...\n");
	TimeWheelMS tw1;
	CTimeWheel* tw = tw1.TimeWheel();

	auto tBefore = tw1.Now();

	std::cout <<  "please change system time\n";

	getchar();

	auto tafter = tw1.Now();

	std::cout << "before:" << tBefore << ", after:" << tafter << std::endl;

	ADD_TIMER(1, 1);
	ADD_TIMER(2, 100);
	ADD_TIMER(3, 5000);

	auto tp1 = tw1.Now();

	const int TEST_DURATION_MS = 10000;
	
	do
	{
		Sleep(50);
		tw1.Run();
	}
	while (tw1.Now()-tp1 < TEST_DURATION_MS);

	printf("TestWrapper end...\n");
}

void TestRestartTimer()
{
	TimeWheelMS objWheel;

	std::cout << "=================" << __FUNCTION__ << "=======================" << std::endl;
	const int RESTART_INTERVAL = 5000;
	TimerPtr m_pTimer = CTimer::CreateNew();
	time_t tTimerStart = objWheel.Now();
	std::function<void(void*)> f = [&](void* arg)
	{
		bool bOK = (objWheel.Now() - tTimerStart) - RESTART_INTERVAL <= 1000;
		std::cout << "ON TIMER:" <<  bOK << std::endl;
		tTimerStart = objWheel.Now();
		m_pTimer->Stop();
		m_pTimer->Start(objWheel.TimeWheel(), RESTART_INTERVAL, f, nullptr, TimerPeriodic::TP_TRUE);
	};
	m_pTimer->Start(objWheel.TimeWheel(), RESTART_INTERVAL, f, nullptr, TimerPeriodic::TP_TRUE);


	time_t tStart = objWheel.Now();
	while (objWheel.Now() - tStart < (RESTART_INTERVAL*3+1000))
	{
		Sleep(rand() % 1000);
		objWheel.Run();
		auto tNow = objWheel.Now();
		std::cout << "delta:" << tNow - tStart << " now:" << tNow << " Remaining:" << m_pTimer->tRemaining << " GetRemain:" << objWheel.TimeWheel()->GetRemain(m_pTimer) << std::endl;
	}
}

void TestPeriodicRemain()
{
	TimeWheelMS objWheel;

	std::cout << "=================" << __FUNCTION__ << "=======================" << std::endl;
	const int PERIODIC_INTERVAL = 1000;
	TimerPtr m_pTimer = CTimer::CreateNew();
	std::function<void(void*)> f = [&objWheel, &m_pTimer, &f](void* arg) { std::cout << "timer:" << objWheel.Now() << std::endl; };
	m_pTimer->Start(objWheel.TimeWheel(), PERIODIC_INTERVAL, f, nullptr, TimerPeriodic::TP_TRUE);


	time_t tStart = objWheel.Now();
	while (objWheel.Now() - tStart < PERIODIC_INTERVAL * 5)
	{
		Sleep(rand() % 100);
		objWheel.Run();
		auto tNow = objWheel.Now();
		std::cout << "delta:" << tNow - tStart << " now:" << tNow << " Remaining:" << m_pTimer->tRemaining << " GetRemain:" << objWheel.TimeWheel()->GetRemain(m_pTimer) << std::endl;
	}
}

void TestFastForward()
{
	TimeWheelMS objWheel;

	std::cout << "=================" << __FUNCTION__ << "=======================" << std::endl;
	const int PERIODIC_INTERVAL = 1000;
	TimerPtr m_pTimer = CTimer::CreateNew();
	std::function<void(void*)> f = [&objWheel, &m_pTimer, &f](void* arg) { std::cout << "PERIODIC:" << objWheel.Now() << std::endl; };
	m_pTimer->Start(objWheel.TimeWheel(), PERIODIC_INTERVAL, f, nullptr, TimerPeriodic::TP_TRUE);

	const int ONCE_INTERVAL = 10000;
	std::function<void(void*)> f2 = [&](void* arg) { std::cout << "ONCE:" << objWheel.Now() << std::endl; };
	TimerPtr m_pTimer2 = CTimer::CreateNew();
	m_pTimer2->Start(objWheel.TimeWheel(), ONCE_INTERVAL, f2, nullptr, TimerPeriodic::TP_FALSE);

	const int FASTFORWARD_INTERVAL = 10000;
	time_t tStart = objWheel.Now();
	int i = 0;
	while (objWheel.Now() - tStart < FASTFORWARD_INTERVAL * 3)
	{
		if (++i % 5 == 0)
		{
			Sleep(rand() % 100 + FASTFORWARD_INTERVAL);
			std::cout << "=FastForward=" << std::endl;
		}
		else
		{
			Sleep(rand() % 1000);
		}
		
		objWheel.Run();
		auto tNow = objWheel.Now();
		std::cout << "delta:" << tNow - tStart << " now:" << tNow << " Remaining:" << m_pTimer->tRemaining << " GetRemain:" << objWheel.TimeWheel()->GetRemain(m_pTimer) << std::endl;
	}
}

int main()
{
	TestTimer();
	TestList();	

	TestWrapper();
	TestRestartTimer();
	TestPeriodicRemain();
	TestFastForward();

	getchar();

	return 0;
}